#  Copyright (c) 2007 Microsoft Corporation. All rights reserved.
$:.unshift File.join(File.dirname(__FILE__),"..")
require	'optparse'
require 'fileutils'
require 'iconv'
require 'rubygems'
require 'rake'
require 'vmxt2tfs/version'
require 'vmxt2tfs/parsers'

#VMXT2TFS is a tool that creates a TFS process template based on a tailored V-Modell XT process.
#
#For more information see VMXT2TFS::Generator or the README.txt
module VMXT2TFS
  #The Generator class is the main application class of VMXT2TFS.
  #
  #It handles command line arguments during initialization.
  #
  #The TFS template created is based on the template constants defined in VMXT2TFS::Templates and the .erb files provided with the scaffolding.
  #
  #These ERB templates are used with the AktivitaetInstanz, Produkt and Entscheidungspunkt objects as well as the Generator itself to create the tailored TFS template.
  #
  #Generator expects a full V-Modell XT export, the VMP file describing the tailoring and the XML export of the tailored process as generated by ProjektAssistent as well as the TFS scaffolding files defining the structure of a TFS process template
  class Generator
    attr_reader :logger,:activities,:milestones,:products,:build_version,:areas,:reports
    #The filename for the expected XML export of the tailored process
    VMXT_FILENAME="Projekt-V-Modell-XT.xml"
    #The directory (relative to the vm-xt root) that contains the exported process
    VMXT_TAILORED="Dokumentation"
    #The require WI types
    REQUIRED_WIS=["VMXT_Produkt.xml","VMXT_Entscheidungspunkt.xml","VMXT_Aktivitaet.xml"]
    def initialize
      parse_command_line
      @logger=VMXT2TFS.setup_logger(@no_file)
      validate_command_line
      check_tfs
      @milestones=Array.new
      @activities=Array.new
      @products=Array.new
      @milestone_index=Hash.new
      @workitem_types=Array.new
      @areas=Array.new
      @iterations=Hash.new
      @build_version=Version::BUILD
    end  
    #Creates the TFS template
    def generate
      begin
        #try to find the vmp file
        vmps=FileList["#{@vmxt}/*.vmp"]
        if vmps.empty?
          @logger.error("Could not find a vmp file")
        end
        @logger.warn("More than one VMP files found! Using the first one") if vmps.size>1
        plan_file=vmps[0]
        @logger.info("Using #{plan_file}")
        @logger.info("XML export file: #{@vmxt_xml}")
        unless File.exists?(@vmxt_xml)
          @logger.fatal("Could not find the xml export file for the tailored process")
          exit 1
        end
        parse_vmxt_files(plan_file)
        deploy_tfs_scaffolding(@tfs,@target)
        create_templated_wits
        determine_workitem_types
        validate_wits
        determine_queries
        Dir.chdir(@tfs) do
          create_default_workitems(File.read(VMXT2TFS::Templates::WORK_ITEMS_TEMPLATE))
          copy_document_templates
          copy_vmp(plan_file)
          copy_html
          copy_css if @vmxt_css
          calculate_wss_tasks_data
          create_templated_document(Templates::PROCESS_FILE,File.read(VMXT2TFS::Templates::PROCESS_TEMPLATE))
          create_templated_document(Templates::CLASSIFICATION_FILE,File.read(VMXT2TFS::Templates::CLASSIFICATION_TEMPLATE))
          create_templated_document(Templates::ROLES_FILE,File.read(VMXT2TFS::Templates::ROLES_TEMPLATE))
          create_templated_document(Templates::WSS_TASKS_FILE,File.read(VMXT2TFS::Templates::WSS_TASKS_TEMPLATE))
        end
        logger.info("Done!")
      rescue
        @logger.debug($!)
        @logger.fatal($!.message)
        exit 1
      end
    end
    # Support templating of member data.
    def get_binding
      binding
    end
    private
    #parses the VM-XT files and populates the binding variables used in the ERB templates
    def parse_vmxt_files plan_file
      parser=CollabXTParser.new(File.read(plan_file),File.read(@vmxt_xml),@logger)
      parser.parse
      @project_name=parser.project_name
      @activities=parser.activities
      @logger.info("#{@activities.size} activities")
      @milestones=parser.milestones
      @logger.info("#{@milestones.size} milestones")
      @products=parser.initial_products
      @logger.info("#{@products.size} products")
      @areas=parser.areas
      @logger.info("#{@areas.size} areas")
      @iterations=parser.iterations
      @logger.info("#{@iterations.size} iterations")
      @groups=parser.roles
      @logger.info("#{@groups.size} groups")
      #get the list of product types
      @product_types=parser.products.collect{ |p| p.title  }
      @product_types.uniq!
    end
    #Copies the TFS template files on the target directory
    def deploy_tfs_scaffolding tfs_source,tfs_target
      @logger.info("Deploying TFS scaffolding to '#{tfs_target}'")
      files=FileList["#{tfs_source}/**/*"].exclude(/\.erb$/)
      files.gsub!("#{tfs_source}/","")
      copy_files(files,tfs_source,tfs_target)
    end
    #Parse and check the command line parameters
    def parse_command_line
      @vmxt=""
      @tfs=""
      @no_file=false
      @vmxt_xml=nil
      @vmxt_css=nil
      #using optparse here, and unfortunately it goes directly to the ARGV array
      ARGV.options do |opt|
        opt.banner
        opt.on("vmxt2tfs [options] -v vmxt_dir -t tfs_dir -x xml_export target_directory")
        opt.on("--vmxt-dir=PATH", "-v=PATH",String,"Directory containing the V Model XT files"){|@vmxt|}
        opt.on("--tfs-dir=PATH", "-t=PATH",String,"Directory containing the TFS template files"){|@tfs|}
        opt.on("--xml=FILE", "-x=FILE",String,"The XML export file of the tailored process"){|@vmxt_xml|}
        opt.on("--css=FILE",String,"The CSS  file for the process guidance HTML"){|@vmxt_css|}
        opt.on("Options:")
        opt.on("--debug", "-d","Debugging mode"){|@debug| $DEBUG=true}
        opt.on("--no-file", "Log to STDOUT"){@no_file=true}
        opt.on("--help", "-h", "-?", "This text") { $stdout.puts opt; exit 0 }
        opt.on("--version", "-V", "Version information") { $stdout.puts(VMXTFS::Version::STRING); exit 0 }
        opt.parse!
      end
    end
    #validates the values of the command line arguments
    def validate_command_line
      @logger.warn("XML export file not provided. Using the default") unless @vmxt_xml
      if File.exists?(@vmxt)
        @vmxt=File.expand_path(@vmxt)
        if File.exists?(@tfs)
          @tfs=File.expand_path(@tfs)
          #try to find the VM-XT XML file if no -x was given
          @vmxt_xml||=File.join(@vmxt,VMXT_TAILORED,VMXT_FILENAME)
          if File.exists?(@vmxt_xml)
            @vmxt_xml=File.expand_path(@vmxt_xml)
            @target=ARGV.shift
            @target||=OUTPUT
            @target=File.expand_path(@target)
          else
            @logger.fatal("XML export file '#{@vmxt_xml}' does not exist")
            exit 1
          end
        else
          @logger.fatal("TFS template directory '#{@tfs}' does not exist")
          exit 1
        end
      else
        @logger.fatal("VMXT tailoring directory '#{@vmxt}' does not exist")
        exit 1
      end
      if @vmxt_css
        if File.exists?(@vmxt_css)
          @vmxt_css=File.expand_path(@vmxt_css)
          @logger.info("Found CSS file '#{@vmxt_css}'")
        else
          @logger.error("CSS file '#{@vmxt_css}' does not exist. Continuing without CSS")
          @vmxt_css=nil
        end
      else
       @logger.error("CSS file not provided. Continuing without CSS")
      end
    end
    #Checks for the conventions of the TFS path
    def check_tfs
      @logger.info("Validating TFS scaffolding")
      prev_dir=Dir.pwd
      Dir.chdir(@tfs)
      VMXT2TFS::Templates::TEMPLATES.each do |t|
        if !File.exists?(t)
          @logger.fatal("Could not find '#{t}' template")
          exit 1
        end
      end
      Dir.chdir(prev_dir)
    end
    #Finds the work item types copied from the TFS scaffolding.
    #
    #Looks in the target directory, #deploy_tfs_scaffolding must run first
    def determine_workitem_types
      @logger.info("Determining work item types")
      src=File.join(@target,"WorkItem Tracking","TypeDefinitions")
      @workitem_types=FileList["#{src}/*.xml"].gsub("#{src}/","")
      #convert to UTF-8 to avoid unpleasantness
      conv = Iconv.new("UTF-8","ISO-8859-1") 
      @workitem_types.collect!{|e| conv.iconv(e)}
      @logger.info("Found #{@workitem_types.size} work item types:\n#{@workitem_types.join("\n")}")
    end
    #validate that Produkt, Aktivitaet and Entscheidungspunkt are present
    def validate_wits
      @logger.info("Validating work item types")
      unless @workitem_types.size-3==(@workitem_types-REQUIRED_WIS).size
        @logger.fatal("Missing one of #{REQUIRED_WIS.join(', ')}")
        exit 1
      end
    end
    #Creates or replaces work item types using the ERB templates defined in Templates::WIT_TEMPLATES
    def create_templated_wits
      @logger.info("Creating templated work item types")
      #change into the type definition directory
      prev_dir=Dir.pwd
      Dir.chdir(File.join(@target,"WorkItem Tracking","TypeDefinitions"))
      VMXT2TFS::Templates::WIT_TEMPLATES.each do |filename,template_file|
        @logger.info("Creating #{filename}")
        template=File.read(File.join(@tfs,template_file))
        if (File.exists?(filename))
          FileUtils.rm_rf(filename)
        end
        File.open(filename,"w"){|fn| fn<<ERB.new(template).result(get_binding)}
      end
    end
    #Finds the work item queries in the TFS scaffolding
    #
    #Looks in the target directory, #deploy_tfs_scaffolding must run first
    def determine_queries
      @logger.info("Determining queries")
      src=File.join(@target,"WorkItem Tracking","Queries")
      @queries=FileList["#{src}/*.wiq"].gsub("#{src}/","")
      #convert to UTF-8 to avoid unpleasantness
      conv = Iconv.new("UTF-8","ISO-8859-1") 
      @queries.collect!{|e| conv.iconv(e)}
      @logger.info("Found #{@queries.size} queries:\n#{@queries.join("\n")}")
    end
    #Creates the workitems.xml using the given template
    def create_default_workitems template
      @logger.info("Creating default work items")
      filename=File.join(@target,"WorkItem Tracking","workitems.xml")
      if (File.exists?(filename))
        FileUtils.rm_rf(filename)
      end
      File.open(filename,"w"){|fn| fn<<ERB.new(template).result(get_binding)}
    end
    #copies the HTMl documentation in the sharepoint area
    def copy_html
      docu_src=File.join(@vmxt,"Dokumentation")
      @logger.info("Copying HTML documentation from #{docu_src}")
      files=FileList["#{docu_src}/**/*.html",
          "#{docu_src}/**/*.gif"].gsub("#{docu_src}/","")
      docu_tgt=File.join(@target,"Windows SharePoint Services","Process Guidance")
      copy_files(files,docu_src,docu_tgt)
    end
    #copies the CSS file
    def copy_css
      docu_tgt=File.join(@target,"Windows SharePoint Services","Process Guidance","exportfiles","style.css")
      mkdir_p(File.dirname(docu_tgt))
      if File.exists?(docu_tgt)
        FileUtils.rm_rf(docu_tgt)
      end
      cp(@vmxt_css,docu_tgt,{:verbose=>false,:preserve=>false})
    end
    #copies the .rtf templates in the sharepoint area
    def copy_document_templates
      docu_src="#{@vmxt}/Produktbibliothek"
      @logger.info("Copying document templates from #{docu_src}")
      files=FileList["#{docu_src}/**/*.rtf"].gsub("#{docu_src}/","")
      docu_tgt=File.join(@target,"Windows SharePoint Services")
      copy_files(files,docu_src,docu_tgt)
    end
    #copies the VMP file in the document library
    def copy_vmp plan_file
      @logger.info("Copying VMP file")
      docu_tgt=File.join(@target,"Windows SharePoint Services","VMXT")
      mkdir_p(docu_tgt,:verbose=>false)
      cp(plan_file,docu_tgt,{:verbose=>false,:preserve=>false})
    end
    #DRY the copying of files in a FileList
    def copy_files files,file_src,file_tgt
      files.each do |file|
        src=File.join(file_src,file)
        tgt=File.join(file_tgt,file)
        mkdir_p(File.dirname(tgt))
        unless File.stat(src).directory?
          if (File.exists?(tgt))
            FileUtils.rm_rf(tgt)
          end
          cp(src,tgt,{:verbose=>false,:preserve=>false}) 
        end
      end
    end
    #Gets the list of files that were copied in Windows SharePoint Services
    def calculate_wss_tasks_data
      @sharepoint_files=Array.new
      @sharepoint_folders=Array.new
      wss_root=File.expand_path(File.join(@target,"Windows SharePoint Services"))
      #work in the wss directory
      Dir.chdir(wss_root) do
        #we discover the document libraries (every directory is considered a library)
        @sharepoint_libraries=Dir.entries(wss_root).select{ |dl| File.directory?(dl) }
        #remove '.' and '..'
        @sharepoint_libraries.delete('.')
        @sharepoint_libraries.delete('..')
        @sharepoint_libraries.compact!
        @logger.info("Found #{@sharepoint_libraries.size} document libraries")
        @sharepoint_libraries.each do |lib|
          lib_root=File.join(wss_root,lib)
          files=FileList["#{lib_root}/**/*"].gsub("#{lib_root}/","")
          Dir.chdir(lib_root){generate_document_library_entries(lib,files)}
        end
        conv = Iconv.new("UTF-8","ISO-8859-1") 
        @sharepoint_libraries.collect!{ |e| conv.iconv(e) }
      end
    end
    #Creates an xml document using the given template
    def create_templated_document filename,template
      @logger.info("Creating #{filename} file")
      filename=File.join(@target,filename)
      if (File.exists?(filename))
        FileUtils.rm_rf(filename)
      end
      File.open(filename,"w"){|fn| fn<<ERB.new(template).result(get_binding)}
    end
    def generate_document_library_entries library,files
        @logger.info("Generating '#{library}' sharepoint entries")
        conv = Iconv.new("UTF-8","ISO-8859-1") 
        #the entries in our array are hashes with the values of the attributes we want to set
        #check WssTasks.erb for usage
        files.each do |e|
          if File.directory?(e)
            @sharepoint_folders<<{:folder=>"#{e}", 
              :library=>conv.iconv(library)}
          else
            @sharepoint_files<<{:source=>"Windows SharePoint Services/#{conv.iconv(library)}/#{conv.iconv(e)}", 
              :library=>conv.iconv(library), 
              :target=>conv.iconv(e)}
          end
        end
    end
  end

  
  #Merge is the functionality class for the vmxt-merge utility
  #
  #Given the VM-XT scaffolding directory, the microprocess TFS template directory and the 
  #target directory containing the .rdl files of the reports you want to use it will
  #create teh appropriate ReportsTasks.xml in the target directory.
  class Merge
    #Filename for the generated file
    FILENAME="Reports/ReportsTasks.xml"
    #Filename of the template to use for generating the reports settings
    TEMPLATE="Reports/ReportsTasks.erb"
    attr_reader :reports
    def initialize
      parse_command_line
      @logger=VMXT2TFS.setup_logger(@no_file,VMXT2TFS::MERGE_LOG)
      validate_command_line
      @reports=Array.new
    end
    
    def merge
      begin
        scaffolding_xml=File.read(File.join(@scaffolding,"Reports","ReportsTasks.xml"))
        micro_xml=File.read(File.join(@micro,"Reports","ReportsTasks.xml"))
        #parse the scaffolding data
        scaffolding_reports=ReportParser.parse_reports(scaffolding_xml,@logger)
        @logger.info("#{scaffolding_reports.size} defined reports in the scaffolding")
        #parse the microprocess data
        micro_reports=ReportParser.parse_reports(micro_xml,@logger)
        all_reports=scaffolding_reports+micro_reports
        @logger.info("#{micro_reports.size} defined reports in the microprocess")
        #get the report files copied to the target directory
        chosen_report_filenames=FileList["#{@target}/Reports/**/*.rdl"]
        @logger.info("#{chosen_report_filenames.size} reports chosen for merging")
        #from all the filenames find the data entries
        chosen_report_filenames.each do |report_filename|
          @logger.info("Merging report #{report_filename}")
          matched_entry=all_reports.select{ |rep| 
            File.basename(File.expand_path(rep.filename))==File.basename(report_filename)  }.compact
          if matched_entry.empty?
            @logger.error("No report information found about '#{report_filename}'")
          else
            @reports+=matched_entry
            @logger.debug("Reports used:\n#{@reports.collect{|r| r.name}.join(",")}")
            #create the ReportsTasks.xml
            if File.exist?(File.join(@scaffolding,TEMPLATE))
              create_templated_document(FILENAME,File.read(File.join(@scaffolding,TEMPLATE)) )
            else
              @logger.fatal("Cannot find #{TEMPLATE} in #{@scaffolding}")
              exit 1
            end
          end
        end
      rescue
        @logger.debug($!)
        @logger.error($!.message)
        exit 1
      end
      @logger.info("Done!")
    end
    # Support templating of member data.
    def get_binding
      binding
    end

    private
    #Parse and check the command line parameters
    def parse_command_line
      @scaffolding=""
      @micro=""
      @no_file=false
      #using optparse here, and unfortunately it goes directly to the ARGV array
      ARGV.options do |opt|
        opt.banner
        opt.on("vmxt-merge [options] -s scaffolding -m microprocess target_directory")
        opt.on("--scaffolding=PATH", "-s=PATH",String,"Directory containing the V Model XT scaffolding files"){|@scaffolding|}
        opt.on("--micro=PATH", "-m=PATH",String,"Directory containing the microprocess TFS template files"){|@micro|}
        opt.on("Options:")
        opt.on("--debug", "-d","Debugging mode"){|@debug| $DEBUG=true}
        opt.on("--no-file", "Log to STDOUT"){@no_file=true}
        opt.on("--help", "-h", "-?", "This text") { $stdout.puts opt; exit 0 }
        opt.on("--version", "-V", "Version information") { $stdout.puts(VMXTFS::Version::STRING); exit 0 }
        opt.parse!
      end
    end
    #Check if the values are sane
    def validate_command_line
      unless File.exist?(@scaffolding)
        @logger.fatal("Scaffolding dircetory '#{@scaffolding}' does not exist")
        exit 1
      end
      unless File.exist?(@micro)
        @logger.fatal("Microprocess dircetory '#{@micro}' does not exist")
        exit 1
      end
      @target=ARGV.shift
      unless File.exist?(@target)
        @logger.fatal("Target directory '#{@target}' does not exist")
        exit 1
      end
      @target=File.expand_path(@target)
    end
    #Creates an xml document using the given template
    def create_templated_document filename,template
      filename=File.join(@target,filename)
      @logger.info("Creating #{filename} file")
      if (File.exists?(filename))
        FileUtils.rm_rf(filename)
      end
      File.open(filename,"w"){|fn| fn<<ERB.new(template).result(get_binding)}
    end
  end
  
end